-- -------------------------------------------------------------------------
-- Lib/io_queue.lua (Deferred IO Queue System)
-- Handles asynchronous-style IO operations to avoid blocking the main loop
-- -------------------------------------------------------------------------

local io_queue = {}

-- Configuration
-- [Lua 5.4] <const> for configuration constants
local MAX_LOGS_PER_FRAME <const> = 10    -- Maximum logs to write per frame
local SAFE_FRAME_TIME_MS <const> = 8     -- Only process IO if frame time < 8ms
local MAX_PENDING_LOGS <const> = 500     -- Maximum queued logs before force flush

-- State
local pending_saves = {}         -- { name = save_function }
local pending_logs = {}          -- { {message, file_handle}, ... }
local log_file_handle = nil      -- Current log file handle
local stats = {
    saves_queued = 0,
    saves_processed = 0,
    logs_queued = 0,
    logs_processed = 0,
    force_flushes = 0,
}

-- [Lua 5.4] <const> for function references
local os_clock <const> = os.clock
local table_insert <const> = table.insert
local table_remove <const> = table.remove
local pairs <const> = pairs
local pcall <const> = pcall
local next <const> = next

-- [Public API]

--- Queue a save operation
--- @param save_func function The function to execute for saving
--- @param name string Unique identifier (prevents duplicate saves)
function io_queue.queue_save(save_func, name)
    pending_saves[name] = save_func
    stats.saves_queued = stats.saves_queued + 1
end

--- Queue a log message for deferred writing
--- @param message string The formatted log message
function io_queue.queue_log(message)
    table_insert(pending_logs, message)
    stats.logs_queued = stats.logs_queued + 1

    -- Force flush if queue is too large (prevent memory buildup)
    if #pending_logs > MAX_PENDING_LOGS then
        io_queue.flush_logs()
        stats.force_flushes = stats.force_flushes + 1
    end
end

--- Set the log file handle for writing
--- @param handle file* The file handle to write logs to
function io_queue.set_log_handle(handle)
    log_file_handle = handle
end

--- Get current log file handle
--- @return file*|nil
function io_queue.get_log_handle()
    return log_file_handle
end

--- Process queued IO operations (call from main loop)
--- @param frame_start_ms number Frame start time in milliseconds
function io_queue.process(frame_start_ms)
    local now_ms = os_clock() * 1000
    local elapsed = now_ms - frame_start_ms

    -- Only process if we have time budget remaining
    if elapsed < SAFE_FRAME_TIME_MS then
        -- Process one save operation per frame
        for name, func in pairs(pending_saves) do
            local ok, err = pcall(func)
            pending_saves[name] = nil
            stats.saves_processed = stats.saves_processed + 1
            if not ok then
                -- Log error but don't crash
                table_insert(pending_logs, "[IO_QUEUE] Save failed: " .. tostring(err))
            end
            break  -- Only one save per frame
        end

        -- Process logs up to limit
        io_queue.process_logs(MAX_LOGS_PER_FRAME)
    end
end

--- Process a specific number of pending logs
--- @param max_count number Maximum logs to process
function io_queue.process_logs(max_count)
    if not log_file_handle then return end

    local count = 0
    while #pending_logs > 0 and count < max_count do
        local msg = table_remove(pending_logs, 1)
        local ok = pcall(function()
            log_file_handle:write(msg .. "\n")
        end)
        if ok then
            stats.logs_processed = stats.logs_processed + 1
        end
        count = count + 1
    end
end

--- Force flush all pending logs immediately
function io_queue.flush_logs()
    if not log_file_handle then return end

    while #pending_logs > 0 do
        local msg = table_remove(pending_logs, 1)
        pcall(function()
            log_file_handle:write(msg .. "\n")
        end)
        stats.logs_processed = stats.logs_processed + 1
    end
end

--- Force process all pending saves immediately
function io_queue.flush_saves()
    for name, func in pairs(pending_saves) do
        pcall(func)
        pending_saves[name] = nil
        stats.saves_processed = stats.saves_processed + 1
    end
end

--- Flush all pending operations (call before shutdown)
function io_queue.flush_all()
    io_queue.flush_saves()
    io_queue.flush_logs()
end

--- Check if there are pending operations
--- @return boolean
function io_queue.has_pending()
    return next(pending_saves) ~= nil or #pending_logs > 0
end

--- Get queue statistics
--- @return table
function io_queue.get_stats()
    return {
        saves_queued = stats.saves_queued,
        saves_processed = stats.saves_processed,
        saves_pending = 0,  -- Will count below
        logs_queued = stats.logs_queued,
        logs_processed = stats.logs_processed,
        logs_pending = #pending_logs,
        force_flushes = stats.force_flushes,
    }
end

--- Get pending save count
--- @return number
function io_queue.pending_save_count()
    local count = 0
    for _ in pairs(pending_saves) do
        count = count + 1
    end
    return count
end

--- Get pending log count
--- @return number
function io_queue.pending_log_count()
    return #pending_logs
end

return io_queue
